using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using Microsoft.WindowsMobile.Status;
using svs.Mobile.BeyondScheduler.Core.Objects.Operations;

namespace svs.Mobile.BeyondScheduler.Core.Objects
{
	public static class TaskRepository
	{
		public class ScheduledTaskList : List<ScheduledTask>
		{
			public void SortByStartTime()
			{
				Sort(new Comparison<ScheduledTask>(ScheduledTask.Compare));
			}
		}

		private static readonly Dictionary<Guid, OperationBase> _Operations = new Dictionary<Guid, OperationBase>();
		private static readonly List<OperationBase> _OperationsForAssignment = new List<OperationBase>(6);
		private static readonly Dictionary<string, ScheduledTask> _ScheduledTasks = new Dictionary<string, ScheduledTask>();
		private static readonly ISchedulingMethod _Device;
		private static readonly string _repositoryStatePath = Configuration.RepositoryFilePath;
		private static DateTime? _LastRepositoryModifiedDate;
		private const string _TaskIDPrefix = "ms#task#";

		public static Dictionary<Guid, OperationBase> Operations
		{
			get { return _Operations; }
		}

		public static ScheduledTaskList ScheduledTasks
		{
			get
			{
				var tasks = new ScheduledTaskList();
				tasks.AddRange(_ScheduledTasks.Values);
				tasks.SortByStartTime();
				return tasks;
			}
		}

		public static List<OperationBase> OperationsForAssignment
		{
			get { return _OperationsForAssignment; }
		}

		/// <summary>
		/// Gets the number of active tasks that can run
		/// </summary>
		public static int ActiveTaskCount
		{
			get
			{
				int active = 0;
				foreach (ScheduledTask task in _ScheduledTasks.Values)
				{
					if (task.Active && task.Event.StartTime.HasValue && task.Event.StartTime.Value > DateTime.Now)
					{
						active++;
					}
				}

				return active;
			}
		}

		#region Constructors

		static TaskRepository()
		{
			/*if (PlatformDetection.IsPocketPC())
				_Device = new SchedulingMethodRunApplicationAtTime();
			else*/
				_Device = new SchedulingMethodSetUserNotification();

			LoadOperations();
			LoadRepository();
			GetScheduledTasks();
		}

		#endregion

		#region Public Methods

		public static bool ExecuteTaskFor(string argument)
		{
			var task = argument == "AppRunAtTime" ? GetLastTaskThatExecutedBefore(DateTime.Now) : GetTaskById(argument);

			if (task == null)
			{
				CoreHelper.WriteLogEntry(string.Format("Unable to find task for key: {0}", argument));
			}
			else
			{
				if (task.CheckIfCanExecute())
					task.Operation.Execute();

				SetScheduledTaskToDevice(task);
				return true;
			}

			return false;
		}

		public static ScheduledTask GetFirstTaskToExecuteAfter(DateTime startTime)
		{
			ScheduledTask retVal = null;
			double difference = int.MaxValue;

			foreach (ScheduledTask task in _ScheduledTasks.Values)
			{
				if (task.Active && task.Event.StartTime.HasValue && startTime < task.Event.StartTime.Value)
				{
					double diff = (task.Event.StartTime.Value - startTime).TotalSeconds;
					if (diff < difference)
					{
						difference = diff;
						retVal = task;
					}
				}
			}

			return retVal;
		}

		/// <summary>
		/// Sets all active tasks to the device and updates their handles
		/// </summary>
		public static void SetScheduledTasksToDevice()
		{
			DeleteScheduledTasksFromDevice();

			foreach (KeyValuePair<string, ScheduledTask> pair in _ScheduledTasks)
			{
				SetScheduledTaskToDevice(pair.Value);
			}
		}

		public static void SetScheduledTaskToDevice(ScheduledTask task)
		{
			_Device.SetScheduledTaskToDevice(task);
		}

		/// <summary>
		/// Reads all the tasks from the Device and updates their handles in the repository
		/// </summary>
		public static void GetScheduledTasks()
		{
			_Device.UpdateScheduledTasks(_ScheduledTasks);
		}

		public static void UnregisterTask(ScheduledTask task)
		{
			if (_ScheduledTasks.ContainsKey(task.TaskID))
				_ScheduledTasks.Remove(task.TaskID);

			if (task.Event.ExistOnDevice)
			{
				DeleteScheduledTaskFromDevice(task);
			}
		}

		/// <summary>
		/// Deletes all scheduled tasks for which we have handles (that that exist on the device)
		/// </summary>
		public static void DeleteScheduledTasksFromDevice()
		{
			foreach (KeyValuePair<string, ScheduledTask> pair in _ScheduledTasks)
			{
				DeleteScheduledTaskFromDevice(pair.Value);
			}
		}

		public static ScheduledTask GetTaskById(string id)
		{
			if (_ScheduledTasks.ContainsKey(id))
				return _ScheduledTasks[id];

			return null;
		}

		public static ScheduledTaskList GetTasksByOperationType(Type type)
		{
			var retVal = new ScheduledTaskList();

			foreach (var task in _ScheduledTasks)
			{
				if(task.Value.Operation != null && task.Value.Operation.GetType().Equals(type))
					retVal.Add(task.Value);
			}

			retVal.SortByStartTime();

			return retVal;
		}

		public static string GenerateUniqueTaskID()
		{
			for (int i = 0; i != 1000; i++)
			{
				string key = GetTaskIDFromNumber(i);
				if (!_ScheduledTasks.ContainsKey(key))
					return key;
			}

			return string.Empty;
		}

		public static int GetTaskNumberFromID(string taskID)
		{
			if (string.IsNullOrEmpty(taskID) || taskID.Length <= _TaskIDPrefix.Length)
				return -1;

			try
			{
				return int.Parse(taskID.Substring(_TaskIDPrefix.Length));
			}
			catch (Exception)
			{
				return -1;
			}
		}

		public static string GetTaskIDFromNumber(int taskNumber)
		{
			return _TaskIDPrefix + taskNumber;
		}

		public static void RegisterTask(ScheduledTask task)
		{
			_ScheduledTasks[task.TaskID] = task;
		}

		public static bool WasRepositoryModifiedExternally()
		{
			DateTime? modified = null;

			//Check the concurrency
			if (File.Exists(_repositoryStatePath))
				modified = File.GetLastWriteTime(_repositoryStatePath);

			return modified.HasValue && _LastRepositoryModifiedDate.HasValue && modified != _LastRepositoryModifiedDate;
		}

		public static void SaveRepository()
		{
			lock (_ScheduledTasks)
			{
				XmlTextWriter tw = null;
				try
				{
					tw = new XmlTextWriter(_repositoryStatePath, Encoding.UTF8);
					tw.WriteStartDocument();

					tw.WriteStartElement("tasks");

					foreach (ScheduledTask task in _ScheduledTasks.Values)
					{
						tw.WriteStartElement("task");
						tw.WriteAttributeString("id", task.TaskID);
						tw.WriteAttributeString("active", task.Active ? "1" : "0");
						tw.WriteAttributeString("minBattLevel", task.MinBatteryLevelRequired.ToString());

						tw.WriteStartElement("event");
						tw.WriteAttributeString("id", task.Event.EventID);
						tw.WriteAttributeString("startTime", task.Event.StartTime.ToString());
						tw.WriteAttributeString("appPath", task.Event.ApplicationPath);
						tw.WriteAttributeString("recPeriod", task.Event.RecurrencePeriod.Ticks.ToString());
						tw.WriteAttributeString("recInterval", task.Event.RecurrenceInterval.ToString());
						tw.WriteEndElement(); //event

						tw.WriteStartElement("operation");
						tw.WriteAttributeString("id", task.Operation.ID.ToString());
						tw.WriteAttributeString("state", task.Operation.SerializedData);
						tw.WriteEndElement(); //operation

						tw.WriteEndElement(); // task
					}
				}
				catch (Exception ex)
				{
					CoreHelper.WriteLogEntry(
						string.Format("Unable to save ScheduledTasks to file: {0} Reason: {1}", _repositoryStatePath, ex.Message));
					throw;
				}
				finally
				{
					if (tw != null)
						tw.Close();

					UpdateLastModifiedDate();
				}
			}
		}

		public static void DeleteScheduledTaskFromDevice(ScheduledTask task)
		{
			_Device.DeleteScheduledTaskFromDevice(task);
		}

		public static string DumpRepository()
		{
			string retVal = string.Empty;
			foreach (var task in _ScheduledTasks.Values)
			{
				retVal += task + "\n";
			}

			return retVal;
		}

		#endregion

		#region Private Methods

		private static ScheduledTask GetLastTaskThatExecutedBefore(DateTime startTime)
		{
			ScheduledTask retVal = null;
			double difference = int.MaxValue;

			foreach (ScheduledTask task in _ScheduledTasks.Values)
			{
				if (task.Active && task.Event.UnadjustedStartTime.HasValue && startTime >= task.Event.UnadjustedStartTime.Value)
				{
					double diff = (startTime - task.Event.UnadjustedStartTime.Value).TotalSeconds;
					if (diff < difference)
					{
						difference = diff;
						retVal = task;
					}
				}
			}

			return retVal;
		}

		private static void UpdateLastModifiedDate()
		{
			if (File.Exists(_repositoryStatePath))
				_LastRepositoryModifiedDate = File.GetLastWriteTime(_repositoryStatePath);
		}

		private static void LoadOperations()
		{
			//List of supported operation types for execution
			_Operations[OperationBluetooth.OperationID] = new OperationBluetooth();
			_Operations[OperationFlightMode.OperationID] = new OperationFlightMode();
			_Operations[OperationWiFi.OperationID] = new OperationWiFi();
			_Operations[OperationStartBeyondPodUpdate.OperationID] = new OperationStartBeyondPodUpdate();
			_Operations[OperationStartApplication.OperationID] = new OperationStartApplication();

			//List of operation instances to be displayed in the "Add Task" dialog
			_OperationsForAssignment.Add(new OperationNull());
			_OperationsForAssignment.Add(new OperationBluetooth(OperationBluetooth.BTAction.On));
			_OperationsForAssignment.Add(new OperationBluetooth(OperationBluetooth.BTAction.Off));
			_OperationsForAssignment.Add(new OperationFlightMode(OperationFlightMode.FMAction.On));
			_OperationsForAssignment.Add(new OperationFlightMode(OperationFlightMode.FMAction.Off));
			_OperationsForAssignment.Add(new OperationWiFi(OperationWiFi.WiFiAction.On));
			_OperationsForAssignment.Add(new OperationWiFi(OperationWiFi.WiFiAction.Off));
			_OperationsForAssignment.Add(new OperationStartBeyondPodUpdate());
			_OperationsForAssignment.Add(new OperationStartApplication());
		}

		private static void LoadRepository()
		{
			if (!File.Exists(_repositoryStatePath))
				return;

			XmlTextReader tr = null;
			try
			{
				tr = new XmlTextReader(_repositoryStatePath);

				object task = tr.NameTable.Add("task");
				object oevent = tr.NameTable.Add("event");
				object operation = tr.NameTable.Add("operation");

				ScheduledTask currentTask = null;

				while (tr.Read())
				{
					if (tr.NodeType == XmlNodeType.Element && ReferenceEquals(tr.LocalName, task))
					{
						currentTask = ReadTask(tr);
					}
					else if (tr.NodeType == XmlNodeType.Element && ReferenceEquals(tr.LocalName, oevent))
					{
						ReadEvent(tr, currentTask);
					}
					else if (tr.NodeType == XmlNodeType.Element && ReferenceEquals(tr.LocalName, operation))
					{
						ReadOperation(tr, currentTask);
					}
				}
			}
			catch (Exception ex)
			{
				CoreHelper.WriteLogEntry("Unable to read feed store! " + ex.Message);
			}
			finally
			{
				if (tr != null)
					tr.Close();

				UpdateLastModifiedDate();
			}
		}

		private static void ReadOperation(XmlReader tr, ScheduledTask task)
		{
			Guid eventID = Guid.Empty;
			string state = String.Empty;

			//Read teh task
			while (tr.MoveToNextAttribute())
			{
				switch (tr.LocalName)
				{
					case "id":
						try
						{
							eventID = new Guid(tr.Value);
						}
						catch
						{
							eventID = Guid.Empty;
						}
						break;

					case "state":
						state = tr.Value;
						break;
				}
			}

			if (Operations.ContainsKey(eventID))
			{
				OperationBase operation = _Operations[eventID].CreateInstance(state);
				task.Operation = operation;
			}
		}

		private static void ReadEvent(XmlReader tr, ScheduledTask task)
		{
			string eventID = string.Empty, appPath = String.Empty;
			DateTime startTime = DateTime.Now;
			long recPeriod = 0;
			int recInterval = 0;

			//Read the task
			while (tr.MoveToNextAttribute())
			{
				switch (tr.LocalName)
				{
					case "id":
						eventID = tr.Value;
						break;

					case "startTime":
						try
						{
							startTime = DateTime.Parse(tr.Value);
						}
						catch
						{
							startTime = DateTime.Now;
						}

						break;

					case "appPath":
						appPath = tr.Value;
						break;

					case "recInterval":
						recInterval = tr.ReadContentAsInt();
						break;

					case "recPeriod":
						recPeriod = tr.ReadContentAsLong();
						break;
				}
			}

			var evt = new Event(eventID)
			          	{
			          		UnadjustedStartTime = startTime,
			          		ApplicationPath = appPath,
			          		RecurrencePeriod = new TimeSpan(recPeriod),
			          		RecurrenceInterval = recInterval
			          	};

			task.Event = evt;
		}

		private static ScheduledTask ReadTask(XmlReader tr)
		{
			string taskID = string.Empty;
			string minBattLevel = string.Empty;
			Boolean active = false;

			//Read teh task
			while (tr.MoveToNextAttribute())
			{
				switch (tr.LocalName)
				{
					case "id":
						taskID = tr.Value;
						break;

					case "minBattLevel":
						minBattLevel = tr.Value;
						break;

					case "active":
						active = tr.ReadContentAsBoolean();
						break;
				}
			}

			BatteryLevel level;

			try
			{
				level = (BatteryLevel) Enum.Parse(typeof (BatteryLevel), minBattLevel, true);
			}
			catch
			{
				level = BatteryLevel.VeryLow;
			}

			var task = new ScheduledTask(taskID)
			           	{
			           		Event = new Event(DateTime.Now, 0, new TimeSpan(1, 0, 0, 0)),
			           		Operation = new OperationNull(),
			           		Active = active,
			           		MinBatteryLevelRequired = level
			           	};

			RegisterTask(task);
			return task;
		}

		#endregion
	}
}